// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "zencore.h"

#include <atomic>
#include <filesystem>
#include <shared_mutex>
#include <string_view>
#include <vector>

#define ZEN_USE_WINDOWS_EVENTS ZEN_PLATFORM_WINDOWS

namespace zen {

void SetCurrentThreadName(std::string_view ThreadName);

/**
 * Reader-writer lock
 *
 * - A single thread may hold an exclusive lock at any given moment
 *
 * - Multiple threads may hold shared locks, but only if no thread has
 *   acquired an exclusive lock
 */
class RwLock
{
public:
	ZENCORE_API void AcquireShared() noexcept;
	ZENCORE_API void ReleaseShared() noexcept;

	ZENCORE_API void AcquireExclusive() noexcept;
	ZENCORE_API void ReleaseExclusive() noexcept;

	struct SharedLockScope
	{
		SharedLockScope(const SharedLockScope& Rhs) = delete;
		SharedLockScope(SharedLockScope&& Rhs) : m_Lock(Rhs.m_Lock) { Rhs.m_Lock = nullptr; }
		SharedLockScope& operator=(SharedLockScope&& Rhs)
		{
			ReleaseNow();
			m_Lock	   = Rhs.m_Lock;
			Rhs.m_Lock = nullptr;
			return *this;
		}
		SharedLockScope& operator=(const SharedLockScope& Rhs) = delete;
		SharedLockScope(RwLock& Lock) : m_Lock(&Lock) { Lock.AcquireShared(); }
		~SharedLockScope() { ReleaseNow(); }

		void ReleaseNow() noexcept
		{
			if (m_Lock)
			{
				m_Lock->ReleaseShared();
				m_Lock = nullptr;
			}
		}

	private:
		RwLock* m_Lock;
	};

	inline void WithSharedLock(auto&& Fun)
	{
		SharedLockScope $(*this);
		Fun();
	}

	struct ExclusiveLockScope
	{
		ExclusiveLockScope(RwLock& Lock) : m_Lock(&Lock) { Lock.AcquireExclusive(); }
		~ExclusiveLockScope() { ReleaseNow(); }

		void ReleaseNow() noexcept
		{
			if (m_Lock)
			{
				m_Lock->ReleaseExclusive();
				m_Lock = nullptr;
			}
		}

	private:
		RwLock* m_Lock;
	};

	inline void WithExclusiveLock(auto&& Fun)
	{
		ExclusiveLockScope $(*this);
		Fun();
	}

private:
	std::shared_mutex m_Mutex;
};

/** Basic abstraction of a simple event synchronization mechanism (aka 'binary semaphore')
 */
class Event
{
public:
	ZENCORE_API Event();
	ZENCORE_API ~Event();

	Event(Event&& Rhs) noexcept : m_EventHandle(Rhs.m_EventHandle) { Rhs.m_EventHandle = nullptr; }

	Event(const Event& Rhs) = delete;
	Event& operator=(const Event& Rhs) = delete;

	inline Event& operator=(Event&& Rhs) noexcept
	{
		std::swap(m_EventHandle, Rhs.m_EventHandle);
		return *this;
	}

	ZENCORE_API void Set();
	ZENCORE_API void Reset();
	ZENCORE_API bool Wait(int TimeoutMs = -1);
	ZENCORE_API void Close();

#if ZEN_USE_WINDOWS_EVENTS
	inline void* GetWindowsHandle() { return m_EventHandle; }
#endif

protected:
	explicit Event(void* EventHandle) : m_EventHandle(EventHandle) {}

	void* m_EventHandle = nullptr;
};

/** Basic abstraction of an IPC mechanism (aka 'binary semaphore')
 */
class NamedEvent
{
public:
	NamedEvent() = default;
	ZENCORE_API explicit NamedEvent(std::string_view EventName);
	ZENCORE_API ~NamedEvent();
	ZENCORE_API void Close();
	ZENCORE_API void Set();
	ZENCORE_API bool Wait(int TimeoutMs = -1);

	NamedEvent(NamedEvent&& Rhs) noexcept : m_EventHandle(Rhs.m_EventHandle) { Rhs.m_EventHandle = nullptr; }

	inline NamedEvent& operator=(NamedEvent&& Rhs) noexcept
	{
		std::swap(m_EventHandle, Rhs.m_EventHandle);
		return *this;
	}

protected:
	void* m_EventHandle = nullptr;

private:
	NamedEvent(const NamedEvent& Rhs) = delete;
	NamedEvent& operator=(const NamedEvent& Rhs) = delete;
};

/** Basic abstraction of a named (system wide) mutex primitive
 */
class NamedMutex
{
public:
	~NamedMutex();

	ZENCORE_API [[nodiscard]] bool Create(std::string_view MutexName);

	ZENCORE_API static bool Exists(std::string_view MutexName);

private:
	void* m_MutexHandle = nullptr;
};

/**
 * Downward counter of type std::ptrdiff_t which can be used to synchronize threads
 */
class Latch
{
public:
	Latch(std::ptrdiff_t Count) : Counter(Count) {}

	void CountDown()
	{
		std::ptrdiff_t Old = Counter.fetch_sub(1);
		if (Old == 1)
		{
			Complete.Set();
		}
	}

	std::ptrdiff_t Remaining() const { return Counter.load(); }

	// If you want to add dynamic count, make sure to set the initial counter to 1
	// and then do a CountDown() just before wait to not trigger the event causing
	// false positive completion results.
	void AddCount(std::ptrdiff_t Count)
	{
		std::atomic_ptrdiff_t Old = Counter.fetch_add(Count);
		ZEN_UNUSED(Old);
		ZEN_ASSERT_SLOW(Old > 0);
	}

	bool Wait(int TimeoutMs = -1)
	{
		std::ptrdiff_t Old = Counter.load();
		if (Old == 0)
		{
			return true;
		}
		return Complete.Wait(TimeoutMs);
	}

private:
	std::atomic_ptrdiff_t Counter;
	Event				  Complete;
};

inline void
SetAtomicMax(std::atomic_uint64_t& Max, uint64_t Value)
{
	while (true)
	{
		uint64_t CurrentMax = Max.load();
		if (Value <= CurrentMax)
		{
			return;
		}
		if (Max.compare_exchange_strong(CurrentMax, Value))
		{
			return;
		}
	}
}

ZENCORE_API int	 GetCurrentThreadId();
ZENCORE_API void Sleep(int ms);

void thread_forcelink();  // internal

}  // namespace zen
